﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

using Flux_Balance_Analysis.Graphs;

using System.Diagnostics;

using Microsoft.SolverFoundation.Common;
using Microsoft.SolverFoundation.Services;

namespace Flux_Balance_Analysis
{
    /// <summary>
    /// Contiene tutti i dati relativi al progetto corrente
    /// e gestisce i dati necessari ai calcoli modificandoli
    /// ad ogni inserimento o rimozione.
    /// </summary>
    public static class FBACore
    {
        #region Properties
        public static FBAGraph GraphToVisualize { get { return _graphToVisualize; } }
        public static int ExternalFluxes { get; private set; }
        public static int InternalFluxes { get; private set; }
        public static string Objective { get; set; }
        public static IEnumerable<Metabolite> Metabolites { get { return _graphToVisualize.Vertices.Cast<Metabolite>(); } }
        public static IEnumerable<Reaction> Reactions { get { return _graphToVisualize.Edges.Cast<Reaction>(); } }
        public static IEnumerable<Constraint> Constraints { get { return model.Constraints; } }
        public static IEnumerable<Decision> Decisions { get { return model.Decisions; } }
        #endregion
        
        #region
        static FBAGraph _graphToVisualize;
        static int counter = 0;
        static SolverContext context;
        static Model model;
        //static Dictionary<string, string> constraints;
        #endregion

        #region
        static FBACore()
        {
            _graphToVisualize = new FBAGraph();
            InternalFluxes = ExternalFluxes = 0;
            //constraints = new Dictionary<string, string>();
            context = SolverContext.GetContext();
            model = context.CreateModel();
        }
        public static Dictionary<string, double> Calculate()
        {
            if (model.Goals.Count() > 0)
            {
                model.RemoveGoal(model.Goals.First());
            }
            
            model.AddGoal("objective", GoalKind.Maximize, Objective);
            
            context.Solve(new SimplexDirective());

            return Decisions.ToDictionary(d => d.Name, d => d.ToDouble());
        }

        public static void Save(string path)
        {
            using (BinaryWriter bw = new BinaryWriter(File.OpenWrite(path)))
            {
                // Salvo la funzione obiettivo
                bw.Write(Objective);

                // Salvo la lista dei metaboliti
                Metabolite[] metabolites = Metabolites.ToArray();
                bw.Write(metabolites.Length);
                foreach (Metabolite metabolite in metabolites)
                {
                    bw.Write(metabolite.Name);
                    bw.Write(metabolite.IsExternal);
                }

                // Salvo la lista delle reazioni
                Reaction[] reactions = Reactions.ToArray();
                bw.Write(reactions.Length);
                foreach (Reaction reaction in reactions)
                {
                    bw.Write(reaction.Name);
                    bw.Write(reaction.Source.ToString());
                    bw.Write(reaction.Target.ToString());
                }

                Constraint[] constraints = Constraints.ToArray();
                bw.Write(constraints.Length);
                foreach (Constraint constraint in constraints)
                {
                    bw.Write(constraint.Name);
                    bw.Write(constraint.Expression);
                    bw.Write(constraint.Enabled);
                }
            }
        }

        public static void Load(string path)
        {
            using (BinaryReader br = new BinaryReader(File.OpenRead(path)))
            {
                // Carico la funzione obiettivo
                Objective = br.ReadString();

                // Carico la lista dei metaboliti
                int length = br.ReadInt32();
                for (int i = 0; i < length; i++)
                {
                    _graphToVisualize.AddVertex(new Metabolite(br.ReadString(), br.ReadBoolean()));
                }

                // Carico le reazioni
                IEnumerable<Metabolite> metabolites = Metabolites;
                length = br.ReadInt32();
                for (int i = 0; i < length; i++)
                {
                    string name = br.ReadString();
                    string src = br.ReadString();
                    string dst = br.ReadString();

                    Metabolite source = metabolites.FirstOrDefault(m => m.Name == src);
                    Metabolite dest = metabolites.FirstOrDefault(m => m.Name == dst);

                    Reaction reaction = new Reaction(name, source, dest);
                    
                    _graphToVisualize.AddEdge(reaction);
                    model.AddDecision(new Decision(Domain.RealNonnegative, reaction.Name));

                    if (reaction.Name[0] == 'B')
                        ExternalFluxes++;
                    else
                        InternalFluxes++;
                }

                length = br.ReadInt32();
                for (int i = 0; i < length; i++)
                {
                    string name = br.ReadString();
                    string expression = br.ReadString();
                    bool enabled = br.ReadBoolean();
                    model.AddConstraint(name, expression).Enabled = enabled;
                    counter++;
                }
            }
        }

        public static void Clear()
        {

        }

        public static bool AddMetabolite(Metabolite metabolite)
        {
            return _graphToVisualize.AddVertex(metabolite);
        }

        public static bool AddReaction(Reaction reaction)
        {
            bool res = model.Decisions.Count(d => d.Name == reaction.Name) <= 0 && _graphToVisualize.AddEdge(reaction);
            if (res)
            {
                Decision decision = new Decision(Domain.RealNonnegative, reaction.Name);
                model.AddDecision(decision);

                if (reaction.Name[0] == 'B')
                    ExternalFluxes++;
                else
                    InternalFluxes++;

                Metabolite src = reaction.Source as Metabolite,
                            dst = reaction.Target as Metabolite;

                if (!src.IsExternal)
                {
                    string expression = "";
                    Constraint constraint = model.Constraints.FirstOrDefault(c => c.Name == src.Name);
                    if (constraint != null)
                    {
                        model.RemoveConstraint(constraint);
                        expression = constraint.Expression.Remove(constraint.Expression.IndexOf(" == 0"));
                    }
                    expression += string.Format(" - ({0})", decision.Name);
                    model.AddConstraint(src.Name, expression.Trim() + " == 0");
                }

                if (!dst.IsExternal)
                {
                    string expression = "";
                    Constraint constraint = model.Constraints.FirstOrDefault(c => c.Name == dst.Name);
                    if (constraint != null)
                    {
                        model.RemoveConstraint(constraint);
                        expression = constraint.Expression.Remove(constraint.Expression.IndexOf(" == 0"));
                    }
                    expression += string.Format(" + ({0})", decision.Name);
                    model.AddConstraint(dst.Name, expression.Trim() + " == 0");
                }
            }
            return res;
        }

        public static void AddConstraint(string name, string expression, bool enabled)
        {
            model.AddConstraint(name, expression).Enabled = enabled;
        }

        public static bool RemoveMetabolite(Metabolite metabolite)
        {
            return _graphToVisualize.RemoveVertex(metabolite);
        }

        public static bool RemoveReaction(Reaction reaction)
        {
            bool res = _graphToVisualize.RemoveEdge(reaction);
            if (res)
            {
                if (reaction.Name[0] == 'B')
                    ExternalFluxes--;
                else
                    InternalFluxes--;

                Metabolite src = reaction.Source as Metabolite,
                dst = reaction.Target as Metabolite;

                Constraint constraint = model.Constraints.FirstOrDefault(c => c.Name == src.Name);
                if (constraint != null)
                {
                    string expression = constraint.Expression.Replace(string.Format(" - ({0})", reaction.Name), "");

                    model.RemoveConstraint(constraint);
                    model.AddConstraint(src.Name, expression);
                }


                constraint = model.Constraints.FirstOrDefault(c => c.Name == dst.Name);
                if (constraint != null)
                {
                    string expression = constraint.Expression.Replace(string.Format(" + ({0})", reaction.Name), "");

                    model.RemoveConstraint(constraint);
                    model.AddConstraint(dst.Name, expression);
                }

                model.RemoveDecision(model.Decisions.FirstOrDefault(d => d.Name == reaction.Name));
            }
            return res;
        }

        public static bool RemoveConstraint(Constraint constraint)
        {
            if (model.Constraints.Contains(constraint))
            {
                model.RemoveConstraint(constraint);
                return true;
            }
            return false;
        }
        #endregion
    }
}
